package com.morse.computer.mvi

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

abstract class BaseViewModel<UiState : IUiState, UiIntent : IUiIntent> : ViewModel() {
    private val _uiStateFlow = MutableStateFlow(initUiState())

    val uiStateFlow: StateFlow<UiState> = _uiStateFlow

    private val _uiIntentFlow: Channel<UiIntent> = Channel()
    val uiIntentFlow: Flow<UiIntent> = _uiIntentFlow.receiveAsFlow()

    private val _loadingIntentFlow: Channel<LoadingIntent> = Channel()
    val loadingIntentFlow: Flow<LoadingIntent> = _loadingIntentFlow.receiveAsFlow()

    protected abstract fun initUiState(): UiState

    protected fun sendUiState(copy:UiState.()->UiState) {
        _uiStateFlow.update { copy(_uiStateFlow.value) }
    }

    fun sendUiIntent(uiIntent: UiIntent) {
        viewModelScope.launch{
            _uiIntentFlow.send(uiIntent)
        }
    }

    init {
        viewModelScope.launch{
            uiIntentFlow.collect {
                handleIntent(it)
            }
        }
    }

    protected abstract fun handleIntent(intent:UiIntent)

    private fun sendLoadUiIntent(loadingIntent: LoadingIntent) {
        viewModelScope.launch{
            _loadingIntentFlow.send(loadingIntent)
        }
    }

    protected fun <T : Any> requestDataWithFlow(
        showLoading: Boolean = true,
        request: suspend ()-> BaseData<T>,
        success:(T) -> Unit,
        failed:suspend (String)-> Unit = {errMsg-> sendLoadUiIntent(LoadingIntent.Error(errMsg))}
    ) {
        viewModelScope.launch {
            if (showLoading) {
                sendLoadUiIntent(LoadingIntent.Loading(true))
            }
            val baseData:BaseData<T>
            try {
                baseData = request()
                when(baseData.state) {
                    ReqState.Success -> {
                        sendLoadUiIntent(LoadingIntent.ShowMainVie)
                        baseData.data?.let {
                            success(it)
                        }
                    }
                    ReqState.Error -> baseData.msg?.let {
                        failed(it)
                    }
                }
            } catch (e: Exception) {
                e.message?.let { failed(it) }
            } finally {
                if (showLoading) {
                    sendLoadUiIntent(LoadingIntent.Loading(false))
                }
            }
        }
    }
}